<?php
declare (strict_types=1);

namespace app\exam\logic\admin;

use app\admin\logic\system\Log;
use app\common\exception\LogicException;
use app\common\logic\category\Category;
use app\exam\event\subscribes\QuestionTopic as QuestionTopicSubscribe;
use app\exam\model\admin\QuestionBank;
use app\exam\model\admin\QuestionTopic as QuestionTopicModel;
use app\exam\model\admin\QuestionTopicMaterial;
use app\exam\model\admin\QuestionTopicOptions;
use app\exam\model\admin\QuestionTopicOptionsCompletion;
use app\exam\model\admin\QuestionTopicOptionsShort;
use think\annotation\Inject;
use think\facade\Event;
use think\helper\Arr;

/**
 * 题库逻辑层
 */
class QuestionTopic extends QuestionTopicModel
{

    /**
     * @Inject()
     *
     * @var QuestionPoint
     */
    protected $questionPoint;

    /**
     * 获取数据
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/10/29
     *
     * @param array $map
     * @return \think\Paginator
     * @throws \think\db\exception\DbException
     */
    public function getList(array $map = [])
    {
        return $this->where($map)
            ->with(['difficultyTitle', 'bankTitleAndCates'])
            ->order('update_time', 'DESC')
            ->paginate($this->getListRows())
            ->each(function ($item) {
                $item->cates = app(Category::class)->parseTitle($item->bank_cates, 3);
                // 试题知识点
                $point = $this->questionPoint->parseTitle($item->getAttr('point'));
                if ($point) {
                    $item->point_str = implode(';', array_column($point, 'title'));
                }
            });
    }

    /**
     * 新建题库
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/24
     *
     * @param array $data
     * @return bool
     */
    public function createTopic(array $data)
    {
        // 检测试题题干、试题类型、试题题库是否填写
        $this->getValidate()->scene('create')->failException()->check($data);

        $base = [
            'question_bank_id'    => $data['bank_id'],
            'question_type'       => $data['type'],
            'title'               => $data['title'],
            'point'               => $this->parsePoint($data['point'] ?? []),
            'difficulty'          => $data['difficulty'] ?? 0,
            'analyze'             => $data['analyze'] ?? '',
            'completion_position' => ($data['type'] == 5) ? ($data['completion_position'] ?? 0) : 0
        ];
        $this->startTrans();
        try {
            // 添加试题
            $res     = $this->create($base);
            $topicId = $res->getAttr('id');
            // 针对不同类型的试题处理
            $this->doCreateTopic($data, $topicId);
            // 题库中的题目数量+1
            QuestionBank::where('id', $data['bank_id'])->inc('num')->update();
            // 知识点的题目数量增加
            QuestionPoint::whereIn('id', $base['point'])->inc('num')->update();
            $this->commit();

            Log::write('新建试题【' . $data['title'] . '】', 'exam', 'create', true);

            return true;
        } catch (LogicException $e) {
            $this->rollback();
            throw $e;
        } catch (\Exception $e) {
            $this->rollback();
            throw new LogicException('添加试题失败', $e->getMessage());
        }
    }

    /**
     * 解析知识点
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/24
     *
     * @param array $point
     * @return array
     */
    protected function parsePoint(array $point)
    {
        return array_column($point, 'id');
    }

    /**
     * 处理试题信息
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/24
     *
     * @param $data
     * @param $topicId
     * @throws \Exception
     */
    protected function doCreateTopic($data, $topicId, $topicType = 1)
    {
        switch ($data['type']) {
            case 1:
            case 2:
            case 3:
            case 4:
                $options = $data['options'];
                // $options = Arr::where($data['options'], function ($var) {
                //     return $var['value'] != '';
                // });
                if (count($options) < 2) {
                    throw new LogicException('至少需要添加2个选项');
                }
                // 单选  多选  判断 不定项
                $answers      = in_array($data['type'], [3, 4]) ? $data['answers'] : [$data['answer']];
                $topicOptions = [];
                foreach ($options as $option) {
                    // 检查空选项
                    if ($option['value'] == '') {
                        throw new LogicException('选项【' . $option['key'] . '】不能为空值');
                    }
                    $topicOptions[] = [
                        'topic_id'   => $topicId,
                        'topic_type' => $topicType,
                        'key'        => $option['key'],
                        'value'      => $option['value'],
                        'is_right'   => in_array($option['key'], $answers)
                    ];
                }

                // 判断题只能有2个选项
                if ($data['type'] == 2 && count($topicOptions) != 2) {
                    throw new LogicException('判断题只能有两个选项');
                }
                // 查找正确答案是否设置正确
                $right      = Arr::where($topicOptions, function ($var) {
                    return $var['is_right'] == 1;
                });
                $rightCount = count($right);
                // 单选/判断题需要有一个答案
                if (in_array($data['type'], [1, 2]) && $rightCount != 1) {
                    throw new LogicException('请检查单选题或判断题正确答案是否设置');
                } else if ($data['type'] == 3 && $rightCount <= 1) {
                    throw new LogicException('多选题正确答案至少为两个');
                } else if ($rightCount == 0) {
                    throw new LogicException('不定项选择题至少有一个正确答案');
                }
                QuestionTopicOptions::where('topic_id', $topicId)->delete();
                (new QuestionTopicOptions())->replace()->saveAll($topicOptions);
                break;
            case 5:
                // 填空题
                $topicOptions = [];
                foreach ($data['options'] as $key => $option) {
                    $item   = [
                        'topic_id' => $topicId,
                        'number'   => $key + 1,
                        'value'    => ''
                    ];
                    $values = Arr::where($option['value'], function ($var) {
                        return $var['val'] != '';
                    });
                    if (empty($values)) {
                        throw new LogicException('请检查答案是否设置正确');
                    }
                    foreach ($values as $k => $value) {
                        $item['value']  = $value['val'];
                        $item['sort']   = $k;
                        $topicOptions[] = $item;
                    }
                }
                QuestionTopicOptionsCompletion::where('topic_id', $topicId)->delete();
                (new QuestionTopicOptionsCompletion())->replace()->saveAll($topicOptions);
                break;
            case 6:
            case 7:
                // 子题
                $this->checkSubTopic($data['sub_topic'], $data['type']);
                // 删除试题
                (new QuestionTopicMaterial())->where('topic_id', $topicId)->delete();
                foreach ($data['sub_topic'] as $k => $topic) {
                    // 添加子题
                    $subTopic = (new QuestionTopicMaterial())->create([
                        'topic_id'      => $topicId,
                        'question_type' => $topic['type'],
                        'title'         => $data['type'] == 6 ? ($topic['title'] ?? '') : '',
                        'point'         => $this->parsePoint($topic['point'] ?? []),
                        'difficulty'    => $topic['difficulty'] ?? 0,
                        'analyze'       => $topic['analyze'] ?? '',
                        'sort'          => $k + 1
                    ]);
                    // 再次调用储存试题选项数据
                    $this->doCreateTopic($topic, $subTopic->getAttr('id'), 2);
                }
                break;
            case 8:
                // 简答题
                $topicOptions = [
                    'topic_id'   => $topicId,
                    'topic_type' => $topicType,
                    'value'      => $data['answer']
                ];

                (new QuestionTopicOptionsShort())->replace()->save($topicOptions);
                break;
        }
    }

    /**
     * 检测子题数据
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/24
     *
     * @param $subTopic
     */
    protected function checkSubTopic($subTopic, $questionType)
    {
        if (count($subTopic) < 1) {
            throw new LogicException('至少需要添加1道小题');
        }
        foreach ($subTopic as $k => $topic) {
            $title = $topic['title'] ?? null;
            if ($questionType == 6 && !$title) {
                throw new LogicException('请检查小题中的题干是否设置');
            }
            // 根据类型检测
            switch ($topic['type']) {
                case 1:
                case 2:
                case 3:
                case 4:
                    // $options = Arr::where($topic['options'], function ($var) {
                    //     return $var['value'] != '';
                    // });
                    $options = $topic['options'];
                    if (count($options) < 2) {
                        throw new LogicException('至少需要添加2个选项');
                    }
                    // 单选  多选  判断 不定项
                    $answers      = in_array($topic['type'], [3, 4]) ? $topic['answers'] : [$topic['answer']];
                    $topicOptions = [];
                    foreach ($options as $option) {
                        // 检查空选项
                        if (!isset($option['value']) || $option['value'] == '') {
                            throw new LogicException('第' . ($k + 1) . '小题的选项【' . $option['key'] . '】不能为空值');
                        }
                        $topicOptions[] = [
                            'is_right' => in_array($option['key'], $answers)
                        ];
                    }
                    // 查找正确答案是否设置正确
                    $right      = Arr::where($topicOptions, function ($var) {
                        return $var['is_right'] == 1;
                    });
                    $rightCount = count($right);
                    // 单选/判断题需要有一个答案
                    if (in_array($topic['type'], [1, 2]) && $rightCount != 1) {
                        throw new LogicException('请检查第' . ($k + 1) . '小题正确答案是否设置');
                    } else if ($rightCount == 0) {
                        throw new LogicException('请检查第' . ($k + 1) . '小题正确答案是否设置');
                    }
                    break;
                case 8:
                    break;
            }
        }
    }

    /**
     * 获取试题答案与选项
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/25
     *
     * @param int $topicId
     * @param int $questionType
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getAnswer(int $topicId, int $questionType, int $topicType = 1)
    {
        $result = [];
        switch ($questionType) {
            case 1:
            case 2:
                // 获取选项
                $options = QuestionTopicOptions::where('topic_id', $topicId)->where('topic_type', $topicType)->select();
                // 解析选项
                $res    = $this->parseAnswer($options);
                $result = [
                    'answer'  => $res['answers'] ? $res['answers'][0] : '',
                    'answers' => [],
                    'options' => $res['options']
                ];
                break;
            case 3:
            case 4:
                // 获取选项
                $options = QuestionTopicOptions::where('topic_id', $topicId)->where('topic_type', $topicType)->select();
                // 解析选项
                $res    = $this->parseAnswer($options);
                $result = [
                    'answer'  => '',
                    'answers' => $res['answers'],
                    'options' => $res['options']
                ];
                break;
            case 5:
                // 填空题选项
                $list    = QuestionTopicOptionsCompletion::where('topic_id', $topicId)->order('number', 'asc')->select();
                $options = [];
                foreach ($list as $option) {
                    if (!isset($options[$option['number']])) {
                        $options[$option['number']] = ['value' => []];
                    }
                    $options[$option['number']]['value'][] = ['val' => $option['value']];
                }

                $result = [
                    'answer'  => '',
                    'answers' => [],
                    'options' => array_values($options)
                ];
                break;
            case 6:
            case 7:
                // 材料题和完形填空子题分析
                $subTopic = QuestionTopicMaterial::with(['difficultyTitle'])->where('topic_id', $topicId)->order('sort', 'asc')->select();
                $result   = [];
                foreach ($subTopic as $topic) {
                    $res      = $this->getAnswer($topic->getAttr('id'), $topic->getAttr('question_type'), 2);
                    $result[] = [
                        'form'    => [
                            'id'                 => $topic->getAttr('id'),
                            'title'              => $topic->getAttr('title'),
                            'type'               => $topic->getAttr('question_type'),
                            'question_type_text' => $topic->getAttr('question_type_text'),
                            'difficulty_title'   => $topic->getAttr('difficulty_title'),
                            'difficulty'         => $topic->getAttr('difficulty'),
                            'point'              => $this->questionPoint->parseTitle($topic->getAttr('point')),
                            'analyze'            => $topic['analyze'],
                            'answer'             => $res['answer'],
                            'answers'            => $res['answers'],
                        ],
                        'options' => $res['options']
                    ];
                }
                break;
            case 8:
                // 简答题
                $answer = QuestionTopicOptionsShort::where('topic_id', $topicId)->where('topic_type', $topicType)->value('value');
                $result = ['answer' => $answer, 'answers' => [], 'options' => []];
                break;
        }

        return $result;
    }

    /**
     * 分析答案与选项
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/25
     *
     * @param $answers
     * @return array[]
     */
    protected function parseAnswer($answers)
    {
        $rightAnswers = [];
        $options      = [];
        foreach ($answers as $option) {
            $options[] = [
                'key'   => $option['key'],
                'value' => $option['value'],
            ];
            if ($option['is_right']) {
                $rightAnswers[] = $option['key'];
            }
        }

        return ['options' => $options, 'answers' => $rightAnswers];
    }

    /**
     * 修改试题
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/25
     *
     * @param $id
     * @param $data
     * @return bool
     */
    public function editTopic($id, $data)
    {
        $topic      = $this->where('id', $id)->findOrFail();
        $data['id'] = $id;
        // 防止更改类型,试题类型不可更改
        $data['type'] = $topic->getAttr('question_type');
        // 检测试题题干、试题类型、试题题库是否填写
        $this->getValidate()->scene('edit')->failException()->check($data);
        // 标题
        $topic->title = $data['title'];
        // 知识点
        $topicPointOriginal = $topic->point;
        $topicPointNew      = $this->parsePoint($data['point'] ?? []);
        if ($pointChange = count(array_diff($topicPointOriginal, $topicPointNew))) $topic->point = $topicPointNew;
        // 难度
        $topic->difficulty = $data['difficulty'];
        // 解析
        $topic->analyze = $data['analyze'] ?? '';
        // 针对完形填空,是否位置循序不限制
        $topic->completion_position = ($data['type'] == 5) ? ($data['completion_position'] ?? 0) : 0;

        $this->startTrans();
        try {
            if (!$topic->save()) {
                throw new LogicException('修改失败');
            }
            if ($pointChange) {
                // 知识点的题目数量增加
                QuestionPoint::whereIn('id', $topicPointOriginal)->dec('num')->update();
                QuestionPoint::whereIn('id', $topicPointNew)->inc('num')->update();
            }
            // 修改内容
            $this->doCreateTopic($data, $id);
            $this->commit();
            // 触发编辑试题事件
            Event::subscribe(QuestionTopicSubscribe::class);
            Event::trigger('EditQuestionTopic', $id);
            Log::write('编辑试题【' . $data['title'] . '】', 'exam', 'update', true);

            return true;
        } catch (LogicException $e) {
            $this->rollback();
            throw $e;
        } catch (\Exception $e) {
            $this->rollback();
        }

        throw new LogicException('修改失败', $e->getMessage());
    }

    /**
     * 删除
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/11/25
     *
     * @param $id
     */
    public function deleteTopic($id)
    {
        $topic = $this->where('id', $id)->findOrFail();
        if ($topic->getAttr('num') > 0) {
            throw new LogicException('试题已有试卷引用，无法删除');
        }
        $this->startTrans();
        try {
            $bankId = $topic->getAttr('question_bank_id');
            $point  = $topic->getAttr('point');
            if (!$topic->delete()) {
                throw new LogicException("删除失败");
            }

            // 更新题库中的题库数量
            QuestionBank::where('id', $bankId)->dec('num')->update();
            // 知识点的题目数量减少
            QuestionPoint::whereIn('id', $point)->dec('num')->update();
            $this->commit();
            Log::write('删除试题【' . $topic->title . '】', 'exam', 'delete', true);
        } catch (LogicException $e) {
            $this->rollback();
            throw $e;
        } catch (\Exception $e) {
            $this->rollback();
        }

    }

    /**
     * 获取选择的试题列表
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/12/20
     *
     * @param array $map
     * @return mixed
     * @throws \think\db\exception\DbException
     */
    public function getChooseList(array $map = [])
    {
        // 查询试题列表
        return $this->where($map)->with(['difficultyTitle'])->paginate($this->getListRows())->each(function ($item) {
            $this->paddingTopicData($item);
        })->append(['question_type_text']);
    }

    /**
     * 填充数据
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/12/22
     *
     * @param $item
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function paddingTopicData(&$item)
    {
        // 试题知识点
        $point = $this->questionPoint->parseTitle($item->getAttr('point'));
        if ($point) {
            $item->point_str = implode(';', array_column($point, 'title'));
        }
        // 根据类型获取试题选项
        $result        = $this->getTopicOptions($item->getAttr('id'), $item->getAttr('question_type'));
        $item->options = $result['options'];
        $item->answers = $result['answers'];

    }

    /**
     * 获取试题内容
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2020/12/20
     *
     * @param $id
     * @param $questionType
     * @return array|array[]
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getTopicOptions($id, $questionType)
    {
        $answer = $this->getAnswer($id, $questionType);
        switch ($questionType) {
            case 1:
            case 2:
                // 单选与判断
                $result = [
                    'answers' => $answer['answer'],
                    'options' => $answer['options']
                ];
                break;
            case 3:
            case 4:
                // 多选与不定项
                $result = [
                    'answers' => implode('', $answer['answers']),
                    'options' => $answer['options']
                ];
                break;
            case 5:
                // 填空题
                $options = [];
                foreach ($answer['options'] as $option) {
                    $res       = implode(' 或 ', array_column($option['value'], 'val'));
                    $options[] = $res;
                }
                $result = [
                    'answers' => [],
                    'options' => $options
                ];
                break;
            case 6:
            case 7:
                // 完形填空和材料题
                $options = [];
                foreach ($answer as $option) {
                    $point   = $option['form']['point'];
                    $point   = $point ? implode(';', array_column($point, 'title')) : '';
                    $answers = '';
                    if (in_array($option['form']['type'], [1, 2, 8])) {
                        // 单,判断,简答(参考答案)
                        $answers = $option['form']['answer'];
                    } else if (in_array($option['form']['type'], [3, 4])) {
                        // 多
                        $answers = implode('', $option['form']['answers']);
                    }
                    $options[] = [
                        'id'                 => $option['form']['id'],
                        'title'              => $option['form']['title'],
                        'question_type'      => $option['form']['type'],
                        'question_type_text' => $option['form']['question_type_text'],
                        'point'              => $point,
                        'difficulty'         => $option['form']['difficulty'],
                        'difficulty_title'   => $option['form']['difficulty_title'],
                        'options'            => $option['options'],
                        'answers'            => $answers,
                        'analyze'            => $option['form']['analyze'],
                    ];
                }
                $result = [
                    'answers' => [],
                    'options' => $options
                ];
                break;
            case 8:
                // 简答题
                $result = [
                    'answers' => $answer['answer'],
                    'options' => []
                ];
                break;
        }

        return $result;
    }

    /**
     * 根据试题类型名称获取试题类型值
     * Author: Martinsun <syh@sunyonghong.com>
     * Date: 2021/3/29
     *
     * @param string $name
     * @return false|int|string
     */
    public function getQuestionTypeByName(string $name)
    {
        $name = rtrim($name, '题');

        return array_search($name, $this->typeOptions);
    }

}
